package com.ccp.dev.workflow.service;

import com.ccp.dev.core.basic.base.BaseService;
import com.ccp.dev.core.basic.util.UUIDUtils;
import com.ccp.dev.core.util.BeanUtils;
import com.ccp.dev.core.util.StringUtil;
import com.ccp.dev.workflow.dao.ExecutionStackDao;
import com.ccp.dev.workflow.model.ExecutionStack;
import com.ccp.dev.workflow.model.NodeCache;
import com.ccp.dev.workflow.model.TaskFork;
import com.ccp.dev.workflow.model.bpm.ProcessCmd;
import com.ccp.dev.workflow.model.bpm.TaskExecutor;
import com.ccp.dev.workflow.service.bpm.thread.TaskThreadService;
import com.ccp.dev.workflow.service.bpm.thread.TaskUserAssignService;
import org.apache.commons.lang.StringUtils;
import org.flowable.bpmn.model.*;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.flowable.task.api.Task;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;

/**
 * ClassDescribe:流程执行堆栈 Service
 * @author :jiayueqiao
 * Date: 2019-07-30
 * Since:1
 */
@Service
public class ExecutionStackService extends BaseService<ExecutionStack> {


    @Resource
    private ExecutionStackDao executionStackDao;
    @Resource
    private TaskUserAssignService taskUserAssignService;
    @Resource
    private TaskService taskService;
    @Resource
    private BpmService bpmService;
    /**
     * 按初始化的流程实例ID构建初始化的堆栈树。
     * <pre>
     * 	实现方法：
     *  1.根据流程实例id查询当前活动的任务。
     *  2.对任务进行遍历。
     *  	1.普通的任务直接在堆栈中添加一条记录。
     *  	2.会签节点会在堆栈中记录 节点id，并把会签的任务id和任务执行人使用逗号分隔记录起来，再添加到堆栈中。
     * </pre>
     * @param procInstId 流程实体id
     */
    @Transactional(rollbackFor = Exception.class)
    public void initStack(String procInstId, List<Task> taskList, String parentStackId){
        if(BeanUtils.isEmpty(taskList)){
            return;
        }
        Map<String,ExecutionStack> nodeIdStackMap=new HashMap<>(15);
        for(Task task:taskList){
            String nodeId=task.getTaskDefinitionKey();
            if(!nodeIdStackMap.containsKey(nodeId)){
                ExecutionStack stack=new ExecutionStack();
                stack.setProcInstId(procInstId);
                stack.setProcDefId(task.getProcessDefinitionId());
                stack.setAssignees(task.getAssignee());
                stack.setDepth(1);
                stack.setParentId(parentStackId);
                stack.setStartTime(new Date());
                stack.setNodeId(nodeId);
                stack.setNodeName(task.getName());
                stack.setTaskIds(task.getId());
                String stackId=UUIDUtils.getUUIDFor32();
                stack.setStackId(stackId);
                stack.setNodePath("0." + stackId + ".");
                nodeIdStackMap.put(nodeId, stack);
            }
            else//为多实例的会签任务
            {
                ExecutionStack stack=nodeIdStackMap.get(nodeId);
                stack.setIsMultiTask(ExecutionStack.MULTI_TASK);
                stack.setAssignees(stack.getAssignees()+","+task.getAssignee());
                stack.setTaskIds(stack.getTaskIds()+ "," + task.getId());
            }
        }

        Iterator<ExecutionStack> stackIt=nodeIdStackMap.values().iterator();
        while(stackIt.hasNext()){
            ExecutionStack exeStack=stackIt.next();
            executionStackDao.add(exeStack);
        }
    }

    /**
     * 获取堆栈树中最新的某个节点
     * @param procInstId 流程实例id
     * @param parentNodeId 父节点id
     * @param taskToken token
     * @return ExecutionStack
     */
    public ExecutionStack getLastestStack(String procInstId,String parentNodeId,String taskToken){
        if(StringUtil.isNotEmpty(taskToken)){
            List<ExecutionStack> list=executionStackDao.getByProcInstIdNodeIdToken(procInstId, parentNodeId,taskToken);
            if(!list.isEmpty()){
                return list.get(0);
            }
            return null;
        }else{
            return getLastestStack(procInstId,parentNodeId);
        }
    }


    /**
     * 获取堆栈树中最新的某个节点
     * @param procInstId 流程实例id
     * @param nodeId 节点id
     * @return ExecutionStack
     */
    public ExecutionStack getLastestStack(String procInstId,String nodeId){
        List<ExecutionStack> list=executionStackDao.getByProcInstIdNodeId(procInstId, nodeId);
        if(!list.isEmpty()){
            return list.get(0);
        }
        return null;
    }
    /**
     * 流程回退时，堆栈需要进行流程命令的参数变更
     * @param processCmd cmd
     * @param taskEntity 任务实体
     * @param taskToken token
     * @return ExecutionStack
     */
    @Transactional(rollbackFor = Exception.class)
    public ExecutionStack backPrepared(ProcessCmd processCmd, TaskEntity taskEntity, String taskToken){
        //若为回退时，需要找出其原来的堆栈树的父节点
        ExecutionStack parentStack=null;
        String instanceId=taskEntity.getProcessInstanceId();
        String procDefId=taskEntity.getProcessDefinitionId();
        String nodeId=taskEntity.getTaskDefinitionKey();
        //指定了其返回的堆栈节点
        if(processCmd.getStackId()!=null){
            parentStack=executionStackDao.getById(processCmd.getStackId());
        }
        //若没有指定其回退的目标节点，动态获取回退节点
        else if(StringUtils.isEmpty(processCmd.getDestTask())){
            ExecutionStack executionStack= getLastestStack(instanceId, nodeId,taskToken);
            if(executionStack!=null )
            {
                parentStack = executionStackDao.getById(executionStack.getParentId());
                if(parentStack != null && parentStack.getProcInstId().equals(executionStack.getProcInstId())){
                    while(nodeId.equals(parentStack.getNodeId())){
                        parentStack = executionStackDao.getById(parentStack.getParentId());
                    }
                    //在往上遍历排除因为自循环引起的问题
                    ExecutionStack preStack= executionStackDao.getById(parentStack.getParentId());

                    while(preStack != null && preStack.getNodeId().equals(parentStack.getNodeId())){
                        parentStack=preStack;
                        preStack=executionStackDao.getById(parentStack.getParentId());
                    }

                }
                //将当前的堆栈加到父堆栈的外出堆栈。
                parentStack.addOutExecutionStack(executionStack);
            }
        }

        //找到父节点，则修改其对应的
        if(parentStack!=null){
            //设置其回退的任务ID
            processCmd.setDestTask(parentStack.getNodeId());
            boolean rtn= NodeCache.isSignTaskNode(procDefId, parentStack.getNodeId());
            //会签不获取之前的人员。
            if(!rtn){
                String assignee= parentStack.getAssignees();
                String[] aryAssignee=assignee.split(",");
                List<TaskExecutor> list=new ArrayList<>();
                for(String userId:aryAssignee){
                    list.add(TaskExecutor.getTaskUser(userId, ""));
                }
                taskUserAssignService.addNodeUser(parentStack.getNodeId(), list);
            }

        }

        return parentStack;

    }

    /**
     * 弹出堆栈。
     * @param parentStack 父堆栈
     */
    @Transactional(rollbackFor = Exception.class)
    public void pop(ExecutionStack parentStack){
        //删除当前堆栈。
        List<ExecutionStack> list=findChildsByParentId(parentStack);
        for(ExecutionStack stack:list){
            executionStackDao.delById(stack.getStackId());
        }
    }

    /**
     * 根据父堆栈查找其下所有的堆栈数据。
     * @param parentStack 父堆栈
     * @return list
     */
    public List<ExecutionStack> findChildsByParentId(ExecutionStack parentStack){
        List<ExecutionStack> allList=new ArrayList<>();
        List<ExecutionStack> list=queryByParentId(parentStack.getStackId());
        allList.addAll(list);
        for(ExecutionStack stack:list){
            recursionByParent(stack.getStackId(),allList);
        }
        return allList;
    }

    /**
     * 按父ID取得所有子结节
     * @param parentId 父if
     * @return list
     */
    private List<ExecutionStack> queryByParentId(String parentId){
        return executionStackDao.queryByParentId(parentId);
    }

    /**
     * 递归访问。
     * @param parentId 父id
     * @param allList list
     */
    private void recursionByParent(String parentId,List<ExecutionStack> allList){
        List<ExecutionStack> list=queryByParentId(parentId);
        if(BeanUtils.isEmpty(list)) {return;}
        allList.addAll(list);
        for(ExecutionStack stack:list){
            recursionByParent(stack.getStackId(),allList);
        }
    }

    /**
     * 把当前任务完成后，产生的新任务加至执行堆栈中去
     * @param actInstId 实体Id
     * @param destNodeId 上一已经完成的任务定义Key
     * @param oldTaskToken 上一已经完成的任务令牌
     * @throws Exception ex
     */
    @Transactional(rollbackFor = Exception.class)
    public void addStack(String actInstId,String destNodeId,String oldTaskToken) throws Exception
    {
        List<Task> taskList= TaskThreadService.getNewTasks();
        if(taskList!=null){
            pushNewTasks(actInstId,destNodeId,taskList,oldTaskToken);
        }

    }

    /**
     * 把一组新的任务加至某个树节点下
     * @param procInstId 流程实例Id
     * @param destNodeId 新的任务挂的父节点的任务(activityId)
     * @param newTasks 流程新任务
     * @param oldTaskToken 上一已经完成的任务令牌
     * @throws Exception ex
     */
    @Transactional(rollbackFor = Exception.class)
    public void pushNewTasks(String procInstId, String destNodeId,List<Task> newTasks,String oldTaskToken) throws Exception{
        if(newTasks.isEmpty()){
            return;
        }
        //获取当前任务在树中的位置
        ExecutionStack curExeNode=getLastestStack(procInstId, destNodeId,oldTaskToken);
        ProcessDefinitionEntity processDef=null;

        //把新增加的任务放堆栈树中去
        Map<String,ExecutionStack> nodeIdStackMap=new HashMap<>(15);
        int i=0;

        //判断当前任务列表中是否存在相同key，若相同则代表目前是分发任务，
        //需要记录，以使后续产生令牌记录不同的任务线上执行的细则
        boolean isIssued=false;
        Set<Task> nodeSet=new HashSet<>(newTasks);
        if(nodeSet.size()<newTasks.size()){
            isIssued=true;
        }

        for(Task task:newTasks){
            i++;
            TaskEntity taskEntity =(TaskEntity)taskService.createTaskQuery().taskId(task.getId()).singleResult();
            if(taskEntity==null) {continue;}

            String nodeId=taskEntity.getTaskDefinitionKey();

            if(processDef==null){
                processDef=bpmService.getProcessDefinitionEntity(taskEntity.getProcessDefinitionId());
            }

            FlowElement taskAct = bpmService.getFlowElement(processDef.getId(), nodeId);
            if(taskAct==null) {
                continue;
            }
            MultiInstanceLoopCharacteristics loopCharacteristics = ((Activity)taskAct).getLoopCharacteristics();
            ExecutionStack stack=nodeIdStackMap.get(nodeId);
            //为多实例的会签任务后续其他实体
            if(null!=loopCharacteristics && stack!=null){
                stack.setIsMultiTask(ExecutionStack.MULTI_TASK);
                stack.setAssignees(stack.getAssignees()+","+task.getAssignee());
                stack.setTaskIds(stack.getTaskIds()+ "," + task.getId());
                executionStackDao.update(stack);
                continue;
            }
            //为普通的任务或第一次添加会签任务至集合中
            String stackId=UUIDUtils.getUUIDFor32();
            stack=new ExecutionStack();
            stack.setProcInstId(taskEntity.getProcessInstanceId());
            stack.setAssignees(taskEntity.getAssignee());
            stack.setProcDefId(taskEntity.getProcessDefinitionId());
            if(curExeNode==null){
                stack.setDepth(1);
                stack.setParentId("0");
                stack.setNodePath("0." + stackId + ".");
            }else{
                stack.setDepth(curExeNode.getDepth()==null?1:curExeNode.getDepth()+1);
                stack.setParentId(curExeNode.getStackId());
                stack.setNodePath(curExeNode.getNodePath() + stackId + ".");
            }

            stack.setStartTime(new Date());
            stack.setNodeId(nodeId);
            stack.setNodeName(taskEntity.getName());
            stack.setTaskIds(taskEntity.getId());
            stack.setStackId(stackId);

            String taskToken=(String)taskService.getVariableLocal(taskEntity.getId(), TaskFork.TAKEN_VAR_NAME);
            if(taskToken!=null){
                stack.setTaskToken(taskToken);
            }else if(stack!=null && isIssued){
                String token="T_"+i;
                taskService.setVariableLocal(taskEntity.getId(),TaskFork.TAKEN_VAR_NAME, token);
                stack.setTaskToken(token);
            }
            executionStackDao.add(stack);
            nodeIdStackMap.put(nodeId, stack);

        }
    }
}
